package com.tcm.service.impl;

import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.tcm.common.enums.CachePrefix;
import com.tcm.entity.User;
import com.tcm.entity.WxUser;
import com.tcm.entity.bo.UserCacheBo;
import com.tcm.exception.*;
import com.tcm.mapper.UserMapper;
import com.tcm.service.*;
import com.tcm.utils.AccessTokenUtil;
import com.tcm.utils.RedisUtil;
import com.wf.captcha.SpecCaptcha;
import com.wf.captcha.base.Captcha;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author Guqier
 * @version 1.0
 * @date 2023/7/2 13:36
 */
@Slf4j
@Service
public class AuthServiceImpl implements AuthService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private UserService userService;

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private RoleService roleService;

    @Autowired
    private PermissionService permissionService;

    @Autowired
    private WxUserService wxUserService;

    /**
     * 管理端登录业务方法
     * @param user 用户信息
     * @return 登录结果Dto
     * @throws UserNotExistException  用户不存在异常
     * @throws EntityInvalidException 实体停用异常
     */
    @Override
    public Map<String, String> login(User user, Integer[] allowUserType) throws UserNotExistException, EntityInvalidException, UserTypeNotInvalidException {
        // 查询当前用户信息
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getAccount, user.getAccount());
        User currUser = userMapper.selectOne(queryWrapper);

        // 检查用户是否存在
        if (currUser == null) {
            throw new UserNotExistException("账户不存在");
        }
        // 检查密码是否正确
        String pwd = DigestUtils.md5DigestAsHex((currUser.getSalt() + user.getPassword()).getBytes());
        if (!pwd.equals(currUser.getPassword())) {
            return null;
        }
        verifyUserQualification(currUser, allowUserType);
        return doLogin(currUser);
    }

    public Map<String, String> doLogin(User currUser) {
        // 生成AccessToken
        String accessToken = AccessTokenUtil.generateAccessToken(currUser);

        // 校验权限
        List<String> userRoleKey = roleService.getUserRoleKey(currUser.getId());
        List<String> permissionKey = permissionService.getPermissionKeyByUserId(currUser.getId());

        // 存入缓存
        UserCacheBo userCacheBo = new UserCacheBo();
        BeanUtils.copyProperties(currUser, userCacheBo);
        userCacheBo.setRoleKeys(userRoleKey);
        userCacheBo.setToken(accessToken);
        userCacheBo.setVisitPermissions(permissionKey);
        redisUtil.setCacheObject(CachePrefix.PC_USER_TOKEN.getCacheKey(String.valueOf(currUser.getId())), userCacheBo, 24, TimeUnit.HOURS);
        // 校验缓存
        log.info("存入缓存账号：{}:{}", CachePrefix.PC_USER_TOKEN.getCacheKey(String.valueOf(currUser.getId())), redisUtil.getCacheObject(CachePrefix.PC_USER_TOKEN.getCacheKey(String.valueOf(currUser.getId()))));
        // 响应结果
        Map<String, String> map = new HashMap<>();
        map.put("token", accessToken);
        return map;
    }

    /**
     * 用户登出业务方法
     * @param accessToken Access Token
     * @return true为成功，false为失败
     */
    @Override
    public boolean logout(String accessToken) {
        Long userId = AccessTokenUtil.getUserId(accessToken);
        return redisUtil.deleteObject(CachePrefix.PC_USER_TOKEN.getCacheKey(String.valueOf(userId)));
    }

    /**
     * 刷新AccessToken
     * @param userId   当前登录用户ID
     * @param account  当前登录用户账号
     * @param oldToken 当前token
     * @return token
     */
    @Override
    public Map<String, String> refreshToken(Long userId, String account, String oldToken) {
        // 保存延迟过滤token到缓存
        redisUtil.setCacheObject(CachePrefix.PC_TOKEN_DELAYED_FILTERING.getCacheKey(String.valueOf(userId)), oldToken, 5, TimeUnit.SECONDS);
        // 生成新Token
        String accessToken = AccessTokenUtil.generateAccessToken(userId, account);
        // 更新缓存信息
        UserCacheBo cacheObjectBo = redisUtil.getCacheObject(CachePrefix.PC_USER_TOKEN.getCacheKey(String.valueOf(userId)));
        cacheObjectBo.setToken(accessToken);
        redisUtil.setCacheObject(CachePrefix.PC_USER_TOKEN.getCacheKey(String.valueOf(userId)), cacheObjectBo, 24, TimeUnit.HOURS);
        // 输出校验
        log.info("刷新Token{}:{}", userId, redisUtil.getCacheObject(CachePrefix.PC_USER_TOKEN.getCacheKey(String.valueOf(userId))));
        // 返回token
        Map<String, String> map = new HashMap<>();
        map.put("token", accessToken);
        return map;
    }

    /**
     * 生成登录验证码并响应
     * @return 生成结果，Base64
     */
    @Override
    public Map<String, String> generateAuthCode() {
        // 生成验证码
        // 三个参数分别为宽、高、位数
        SpecCaptcha specCaptcha = new SpecCaptcha(120, 40, 4);
        // 设置类型，纯数字、纯字母、字母数字混合
        specCaptcha.setCharType(Captcha.TYPE_DEFAULT);
        String uuid = UUID.randomUUID().toString();
        // 保存到缓存
        redisUtil.setCacheObject(CachePrefix.PC_AUTH_CODE_UUID.getCacheKey(uuid), specCaptcha.text(), 5, TimeUnit.MINUTES);
        log.info("Code缓存{}:{}", CachePrefix.PC_AUTH_CODE_UUID.getCacheKey(uuid), specCaptcha.text());
        Map<String, String> map = new HashMap<>();
        map.put("uuid", uuid);
        map.put("image", specCaptcha.toBase64());
        return map;
    }

    /**
     * 校验验证码
     * @param uuid 验证码UUID
     * @param code 验证码
     * @return 验证结果，true为成功。
     */
    @Override
    public boolean verifyAuthCode(String uuid, String code) {
        if (code == null) {
            return false;
        }
//        if (code.equals("-1")) return true;// 测试跳过

        code = code.toLowerCase();// 转换为小写字母
        String authCode = redisUtil.getCacheObject(CachePrefix.PC_AUTH_CODE_UUID.getCacheKey(uuid));
        if (authCode != null) {
            authCode = authCode.toLowerCase();// 转换为小写字幕
            if (authCode.equals(code)) {
                redisUtil.deleteObject(CachePrefix.PC_AUTH_CODE_UUID.getCacheKey(uuid));
                return true;
            }
        }
        return false;
    }

    /**
     * 微信用户登录
     * @param session       WxMaJscode2SessionResult
     * @param allowUserType 允许登录用户类型
     * @return 登录结果
     * @throws WxUserNotBindException 微信用户没有绑定账户异常
     */
    @Override
    public Map<String, String> wxLogin(WxMaJscode2SessionResult session, Integer[] allowUserType) throws WxUserNotBindException {
        // 查询openid是否与用户绑定
        User wxBindUser = wxUserService.getWxBindUserByOpenid(session.getOpenid());
        if (wxBindUser == null) {
            throw new WxUserNotBindException();
        }
        // 校验用户资格
        verifyUserQualification(wxBindUser, allowUserType);
        return doLogin(wxBindUser);
    }

    /**
     * 校验用户是否有登录资格
     * @param user          用户对象
     * @param allowUserType 允许用户登录类型
     */
    public void verifyUserQualification(User user, Integer[] allowUserType) {
        // 判断用户类型是否允许登录
        List<Integer> allowUserTypeList = Arrays.asList(allowUserType);
        if (!allowUserTypeList.contains(user.getUserType())) {
            throw new UserTypeNotInvalidException("该账户类型未被允许");
        }
        // 检查用户是否处于有效期
        if (user.getDisableTime() != null) {
            if (new Date().getTime() >= user.getDisableTime().getTime()) {
                throw new EntityInvalidException("账户已过期");
            }
        }
        // 检查用户是否停用（冗余）
        if (user.getStatus() == 1) {
            throw new EntityInvalidException("账户已停用");
        }
    }

    /**
     * 绑定微信账号
     * @param openid 微信openid
     * @param userId 当前用户ID
     * @return 绑定结果
     */
    @Override
    @Transactional
    public boolean miniAppBind(String openid, Long userId) {
        // 校验账号是否被绑定
        LambdaQueryWrapper<WxUser> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(WxUser::getUserId, userId);
        if (wxUserService.count(queryWrapper) != 0) {
            throw new EntityAlreadyExistException("账号已被绑定");
        }
        // 保存绑定信息
        User user = userService.getById(userId);
        WxUser wxUser = new WxUser();
        wxUser.setOpenid(openid);
        wxUser.setUserId(userId);
        wxUser.setAccount(user.getAccount());
        return wxUserService.save(wxUser);
    }

    @Override
    @Transactional
    public boolean miniAppUnbind(Long userId){
        return wxUserService.remove(new LambdaQueryWrapper<WxUser>().eq(WxUser::getUserId, userId));
    }

}
